package com.lry.basic.juc;

import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author:刘仁有
 * @desc:
 * @email:953506233@qq.com
 * @data:2019/7/6
 */
public class MyTimer {

    private final TaskQueue queue = new TaskQueue();
    private final TimerThread thread = new TimerThread(queue);

    private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
    private static int serialNumber() {
        return nextSerialNumber.getAndIncrement();
    }
    public MyTimer() {
        this("MyTimer-" + serialNumber());
    }
    public MyTimer(String name) {
        thread.setName(name);
        thread.start();
    }
    public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        sched(task, System.currentTimeMillis()+delay, 0);
    }
    public void schedule(TimerTask task, Date time) {
        sched(task, time.getTime(), 0);
    }

    public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
    }

    public void schedule(TimerTask task, Date firstTime, long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), -period);
    }

    public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, period);
    }


    public void scheduleAtFixedRate(TimerTask task, Date firstTime,
                                    long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), period);
    }

    private void sched(TimerTask task, long time, long period) {
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");

        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;
        synchronized(queue) {
            synchronized(task.lock) {
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                            "Task already scheduled or cancelled");
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }
            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }

    class TimerThread extends Thread{
        private TaskQueue queue;

        TimerThread(TaskQueue queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            mainLoop();
        }
        private void mainLoop() {
            while (true) {
                try {
                    TimerTask task;
                    boolean taskFired;
                    synchronized(queue) {

                        while (queue.isEmpty())
                            queue.wait();
                        if (queue.isEmpty())
                            break;

                        long currentTime, executionTime;
                        task = queue.getMin();
                        synchronized(task.lock) {
                            if (task.state == TimerTask.CANCELLED) {
                                queue.removeMin();
                                continue;
                            }
                            currentTime = System.currentTimeMillis();
                            executionTime = task.nextExecutionTime;
                            if (taskFired = (executionTime<=currentTime)) {
                                if (task.period == 0) {
                                    queue.removeMin();
                                    task.state = TimerTask.EXECUTED;
                                } else {
                                    queue.rescheduleMin(
                                            task.period<0 ? currentTime   - task.period
                                                    : executionTime + task.period);
                                }
                            }
                        }
                        if (!taskFired)
                            queue.wait(executionTime - currentTime);
                    }
                    if (taskFired)
                        task.run();
                } catch(InterruptedException e) {
                }
            }
        }
    }

    class TaskQueue{
        private TimerTask[] queue = new TimerTask[128];

        private int size = 0;

        int size(){
            return size;
        }
        boolean isEmpty() {
            return size==0;
        }

        void add(TimerTask task){
            if (size + 1 == queue.length)
                queue = Arrays.copyOf(queue, 2*queue.length);
            queue[++size] = task;
            fixUp(size);
        }

        TimerTask getMin(){
            return queue[1];
        }

        void removeMin(){
            queue[1] = queue[size];
            queue[size--] = null;
            fixDown(1);
        }

        void rescheduleMin(long newTime) {
            queue[1].nextExecutionTime = newTime;
            fixDown(1);
        }
        private void fixDown(int k) {
            int j;
            while ((j = k << 1) <= size && j > 0) {
                if (j < size &&
                        queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
                    j++; // j indexes smallest kid
                if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                    break;
                TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
                k = j;
            }
        }

        private void fixUp(int k) {
            while(k>1){
                int j = k>>1;
                if(queue[j].nextExecutionTime<=queue[k].nextExecutionTime){
                    break;
                }
                TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
                k = j;
            }
        }

    }

}
